/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.thienloc.omap.controller.report;

import com.thienloc.omap.controller.BaseBean;
import com.thienloc.omap.entity.SmsKeywordServiceMapping;
import com.thienloc.omap.entity.SyntaxDefinition;
import com.thienloc.omap.entity.report.BroadcastProductionOfSyntax;
import com.thienloc.omap.entity.report.BroadcastProductionOfUser;
import com.thienloc.omap.jdbc.dao.TblSmsBroadcastingDao;
import com.thienloc.omap.session.SmsKeywordServiceMappingFacade;
import com.thienloc.omap.session.SyntaxDefinitionFacade;
import com.thienloc.omap.util.JsfUtil;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.EJB;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author GiangLT
 */
@ManagedBean
@ViewScoped
public class ProductionOfBroadcastBean extends BaseBean implements Serializable {

    @EJB
    private SmsKeywordServiceMappingFacade keywordServiceMappingFacade;
    @EJB
    private SyntaxDefinitionFacade syntaxDefinitionFacade;
    private final Logger logger = LoggerFactory.getLogger(ProductionOfBroadcastBean.class);
    private String month;
    private String year;
    private Date day;
    private List<String> selectedProviders;
    private List<String> unmatchSmses;
    private String unmatchSms;
    private List<BroadcastProductionOfUser> list = new ArrayList<BroadcastProductionOfUser>();
    private Map<String, BroadcastProductionOfUser> userMap = new HashMap<String, BroadcastProductionOfUser>(0);
    private final String BY_DAY = "byDay";
    private final String BY_MONTH = "byMonth";
    private String reportType = BY_DAY;
    private ThreadPoolExecutor executor;
    public Boolean isProcessing = false;
    private static final int CORE_POOL_SIZE = 10;
    private static final int MAXIMUM_POOL_SIZE = 15;

    @PostConstruct
    public void postConstruct() {
        executor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
        Calendar cal = Calendar.getInstance();
        int thisMonth = cal.get(Calendar.MONTH) + 1;
        day = cal.getTime();
        month = getMonthOrDayString(thisMonth);
        year = String.valueOf(cal.get(Calendar.YEAR));
    }

    @PreDestroy
    public void preDestroy() {
        executor.shutdown(); // Disable new tasks from being submitted
        try {
            // Wait a while for existing tasks to terminate
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                executor.shutdownNow(); // Cancel currently executing tasks
                // Wait a while for tasks to respond to being cancelled
                if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                    logger.error("Executor did not terminate");
                }
            }
        } catch (InterruptedException ie) {
            // (Re-)Cancel if current thread also interrupted
            executor.shutdownNow();
//            // Preserve interrupt status
//            Thread.currentThread().interrupt();
        }
    }

    public void reportOfThisMonth() {
        Calendar cal = Calendar.getInstance();
        int thisMonth = cal.get(Calendar.MONTH) + 1;
        month = getMonthOrDayString(thisMonth);
        year = String.valueOf(cal.get(Calendar.YEAR));
        report();
    }

    public void reportOfLastMonth() {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.MONTH, -1);
        int lastMonth = cal.get(Calendar.MONTH) + 1;
        month = getMonthOrDayString(lastMonth);
        year = String.valueOf(cal.get(Calendar.YEAR));
        report();
    }

    public void reportByDay() {
        Calendar cal = Calendar.getInstance();
        cal.setTime(day);
        int thisMonth = cal.get(Calendar.MONTH) + 1;
        month = getMonthOrDayString(thisMonth);
        year = String.valueOf(cal.get(Calendar.YEAR));
        report();
    }

    public void reportOfToday() {
        Calendar cal = Calendar.getInstance();
        int thisMonth = cal.get(Calendar.MONTH) + 1;
        day = cal.getTime();
        month = getMonthOrDayString(thisMonth);
        year = String.valueOf(cal.get(Calendar.YEAR));
        report();
    }

    public void report() {
        try {
            unmatchSmses = new ArrayList<String>();
            isProcessing = true;
            //lấy danh sách người vận hành
            List<String> operatorUsers = syntaxDefinitionFacade.findAllUsername();
            userMap = new HashMap<String, BroadcastProductionOfUser>(operatorUsers.size());

            for (String username : operatorUsers) {
                BroadcastProductionOfUser broadcastProductionOfUser = new BroadcastProductionOfUser();
                broadcastProductionOfUser.setUsername(username);

                List<SyntaxDefinition> managingSyntaxes;
                try {
                    managingSyntaxes = syntaxDefinitionFacade.findByUsername(username);
                    for (SyntaxDefinition managingSyntax : managingSyntaxes) {
                        BroadcastProductionOfSyntax bpos = new BroadcastProductionOfSyntax(managingSyntax.getSyntax(), 0); //default = 0
                        bpos.setMdnSuffixEndRange(managingSyntax.getMdnSuffixEndRange());
                        bpos.setMdnSuffixStartRange(managingSyntax.getMdnSuffixStartRange());
                        broadcastProductionOfUser.addSyntax(bpos);
                    }
                    userMap.put(username, broadcastProductionOfUser);
                } catch (Exception ex) {
                    logger.error("", ex);
                    JsfUtil.addErrorMessage("Lỗi", ex.getMessage());
                }
            }

            String dayStr = getMonthOrDayString(day.getDate());
            //lấy toàn bộ các sms đã shot
            logger.trace("[{}] Counting all broadcasted messages", getUsername());
            TblSmsBroadcastingDao smsBroadcastingDao = new TblSmsBroadcastingDao();
            Map<String, Integer> smsBroadcastingMap;
            if (reportType.equals(BY_MONTH)) {
                smsBroadcastingMap = smsBroadcastingDao.find(month, year, selectedProviders);
            } else {
                smsBroadcastingMap = smsBroadcastingDao.find(dayStr, month, year, selectedProviders);
            }
            logger.trace("[{}] Total {} broadcasted messages", getUsername(), smsBroadcastingMap.size());

            Set<String> broadcastedSmses = smsBroadcastingMap.keySet();
            List<SmsKeywordServiceMapping> keywordServiceMappings = keywordServiceMappingFacade.findAll();

            Collections.sort(keywordServiceMappings, new Comparator<SmsKeywordServiceMapping>() {

                @Override
                public int compare(SmsKeywordServiceMapping o1, SmsKeywordServiceMapping o2) {
                    String keyword1 = o1.getContentKeyword();
                    String keyword2 = o2.getContentKeyword();
                    return keyword2.compareToIgnoreCase(keyword1);
                }
            });
            
            for (String broadcastedSms : broadcastedSmses) {

                Integer broadcastedSmsProduction = smsBroadcastingMap.get(broadcastedSms);
                boolean matchFound = false;
                for (SmsKeywordServiceMapping smsKeywordServiceMapping : keywordServiceMappings) {

                    if (broadcastedSms.toUpperCase().contains(smsKeywordServiceMapping.getContentKeyword().toUpperCase())) {
                        String syntax = smsKeywordServiceMapping.getSyntaxPrefix();
//                        logger.trace("Found syntax {} (production {}) for broadcasted message: {}",
//                                new Object[]{broadcastedSmsProduction, syntax, broadcastedSms});

                        List<SyntaxDefinition> syntaxDefinitions = syntaxDefinitionFacade.findBySyntax(syntax);
                        if (syntaxDefinitions.size() == 1
                                && syntaxDefinitions.get(0).getMdnSuffixEndRange() == -1
                                && syntaxDefinitions.get(0).getMdnSuffixStartRange() == -1) {
                            //=> cú pháp này chỉ có 1 người sử dụng
                            String username = syntaxDefinitions.get(0).getUsername();
                            BroadcastProductionOfUser broadcastProductionOfUser = userMap.get(username);
                            broadcastProductionOfUser.increaseProduction(syntax, broadcastedSmsProduction);
                            broadcastProductionOfUser.addMessageToSyntax(syntax, broadcastedSms);
                            broadcastProductionOfUser.setUniqueOperator(true);
//                            logger.trace("Add {} production to syntax {} for user {}", 
//                                    new Object[]{smsBroadcastingMap.get(broadcastedSms), syntax, username});
//                            logger.debug("[{}]: {}", new Object[]{getUsername(), broadcastProductionOfUser.toString()});
                        } else {
                            //cú pháp có >2 người sử dụng
                            logger.debug("[{}]: 2 users syntax {} ({})", new Object[]{getUsername(), syntax, broadcastedSmsProduction});
                            for (SyntaxDefinition syntaxDefinition : syntaxDefinitions) {
                                String username = syntaxDefinition.getUsername();
                                BroadcastProductionOfUser broadcastProductionOfUser = userMap.get(username);
                                broadcastProductionOfUser.addMessageToSyntax(syntax, broadcastedSms);
                                broadcastProductionOfUser.setUniqueOperator(false);
                            }
                        }

                        matchFound = true;
                        break;
                    }

                }
                if (!matchFound) {
                    logger.debug("No match found ({}):  {}", broadcastedSmsProduction, broadcastedSms);
                    unmatchSmses.add("Production: " + broadcastedSmsProduction);
                    unmatchSmses.add("Sms: " + broadcastedSms);
                    unmatchSmses.add("----------------------------------------------------------------");
                    unmatchSmses.add("");
                }
            }

            list = new ArrayList<BroadcastProductionOfUser>(userMap.values());
            for (BroadcastProductionOfUser broadcastProductionOfUser : list) {
                if (!broadcastProductionOfUser.getUniqueOperator()) {
                    List<BroadcastProductionOfSyntax> broadcastProductionOfSyntaxs = new ArrayList<BroadcastProductionOfSyntax>(broadcastProductionOfUser.getProductionOfSyntax());
                    for (BroadcastProductionOfSyntax broadcastProductionOfSyntax : broadcastProductionOfSyntaxs) {
                        Integer mdnSuffixStartRange = broadcastProductionOfSyntax.getMdnSuffixStartRange();
                        Integer mdnSuffixEndRange = broadcastProductionOfSyntax.getMdnSuffixEndRange();
                        List<String> smses = broadcastProductionOfSyntax.getMessages();

                        if (mdnSuffixEndRange != null && mdnSuffixStartRange != null && smses != null && !smses.isEmpty()) {

                            logger.debug("Counting for syntax {}", broadcastProductionOfSyntax.getSyntax());
                            Integer numOfPages = smses.size() / 1000;
                            logger.debug("Broadcasted sms list length: {}", smses.size());
                            
                            String[] smsArray = smses.toArray(new String[0]);
                            for (int i = 0; i <= numOfPages; i++) {
                                Integer listStartIdx = i * 1000;
                                Integer listEndRange;
                                if (i == numOfPages) {
                                    listEndRange = smses.size() + 1;
                                } else {
                                    listEndRange = listStartIdx + 999;
                                }

                                logger.debug("Array of smses from {} to {}", listStartIdx, listEndRange);
                                List<String> smsList = Arrays.asList(Arrays.copyOfRange(smsArray, listStartIdx, listEndRange));

                                ProductionCountThread productionCountThread = new ProductionCountThread();
                                productionCountThread.setBroadcastProductionOfSyntax(broadcastProductionOfSyntax);
                                productionCountThread.setDayStr(dayStr);
                                productionCountThread.setMdnSuffixEndRange(mdnSuffixEndRange);
                                productionCountThread.setMdnSuffixStartRange(mdnSuffixStartRange);
                                productionCountThread.setSmsList(smsList);
                                executor.execute(productionCountThread);

                            }
                        }
                    }
                }
            }
            JsfUtil.addMessage(FacesMessage.SEVERITY_WARN, "Đang diễn ra", "Quá trình tính toán đang diễn ra, kết quả được cập nhật liên tục");
        } catch (Exception ex) {
            JsfUtil.addErrorMessage("Lỗi", ex.getMessage());
            logger.error("[{}] ", getUsername(), ex);
        }
    }

    public void checkProgress() {
        long total = executor.getTaskCount();
        long completed = executor.getCompletedTaskCount();

        if (total == completed) {
            isProcessing = false;
            logger.debug("Polling: DONE!");
            JsfUtil.addSuccessMessage("Hoàn tất", "Quá trình tính toán đã hoàn tất");
            try {
                exportToFile();
            } catch (Exception ex) {
                logger.error("", ex);
            }
        } else {
            logger.debug("Polling: not done yet");
            JsfUtil.addMessage(FacesMessage.SEVERITY_WARN, "Đang diễn ra", "Quá trình tính toán đang diễn ra, kết quả được cập nhật liên tục");
        }
    }

    private String getMonthOrDayString(int monthOrDay) {
        if (monthOrDay < 10) {
            return "0" + String.valueOf(monthOrDay);
        } else {
            return String.valueOf(monthOrDay);
        }
    }

    private void exportToFile() throws IOException {
        File resultFile;
        File unmatchFile;

        if (reportType.equals(BY_DAY)) {
            resultFile = new File(System.getProperty("user.home")
                    + "/log/omap/san-luong-broadcast-"
                    + (new SimpleDateFormat("yyyyMMdd")).format(day)
                    + "-"
                    + (new SimpleDateFormat("yyyyMMddHHmmss")).format(new Date()));

            unmatchFile = new File(System.getProperty("user.home")
                    + "/log/omap/sms-ko-phan-tich-duoc-"
                    + (new SimpleDateFormat("yyyyMMdd")).format(day)
                    + "-"
                    + (new SimpleDateFormat("yyyyMMddHHmmss")).format(new Date()));

        } else {
            resultFile = new File(System.getProperty("user.home")
                    + "/log/omap/san-luong-broadcast-"
                    + year + "-" + month + "-"
                    + (new SimpleDateFormat("yyyyMMddHHmmss")).format(new Date()));
            unmatchFile = new File(System.getProperty("user.home")
                    + "/log/omap/sms-ko-phan-tich-duoc-"
                    + year + "-" + month + "-"
                    + (new SimpleDateFormat("yyyyMMddHHmmss")).format(new Date()));
        }

        List<String> lines = new ArrayList<String>();
        lines.add("Mang: ");
        for (String provider : selectedProviders) {
            lines.add(provider);
        }
        lines.add("");
        for (BroadcastProductionOfUser bpou : list) {
            lines.add("********** Nguoi van hanh: " + bpou.getUsername()
                    + " | Tong: " + bpou.getTotalProduction()
                    + " **********");
            lines.add("");
            for (BroadcastProductionOfSyntax bpos : bpou.getProductionOfSyntax()) {
                lines.add("====================================");
                lines.add("Cu phup: " + bpos.getSyntax()
                        + " | Duoi so thue bao bat dau tu: " + bpos.getMdnSuffixStartRange() + " den: " + bpos.getMdnSuffixEndRange()
                        + " | San luong: " + bpos.getProduction());
                for (String message : bpos.getMessages()) {
                    lines.add(message);
                    lines.add("------------------------------------");
                }

            }
        }

        FileUtils.writeLines(resultFile, lines);
        if (!unmatchSmses.isEmpty()) {
            FileUtils.writeLines(unmatchFile, unmatchSmses);
        }
    }

    private class ProductionCountThread implements Runnable {

        private List<String> smsList;
        private String dayStr;
        private Integer mdnSuffixStartRange;
        private Integer mdnSuffixEndRange;
        private BroadcastProductionOfSyntax broadcastProductionOfSyntax;

        @Override
        public void run() {
            try {
                logger.trace(">>> Start Result: syntax {}, range {}-{}, smsList size {}", 
                                    new Object[]{broadcastProductionOfSyntax.getSyntax(), mdnSuffixStartRange, mdnSuffixEndRange, smsList.size()});
                Integer production;
                TblSmsBroadcastingDao smsBroadcastingDao = new TblSmsBroadcastingDao();
                if (reportType.equals(BY_MONTH)) {
                    logger.debug("count by month");
                    production = smsBroadcastingDao.countProduction(smsList, selectedProviders, month, year, mdnSuffixStartRange, mdnSuffixEndRange);
                } else {
                    logger.debug("count by day");
                    production = smsBroadcastingDao.countProduction(smsList, selectedProviders, dayStr, month, year, mdnSuffixStartRange, mdnSuffixEndRange);
                }
                logger.trace("<<< End Result: syntax {}(production: {}), range {}-{}, smsList size {}", 
                                    new Object[]{broadcastProductionOfSyntax.getSyntax(), production, mdnSuffixStartRange, mdnSuffixEndRange, smsList.size()});
                broadcastProductionOfSyntax.addProduction(production);
                logger.debug("Finish count...");
            } catch (Exception ex) {
                LoggerFactory.getLogger(ProductionCountThread.class).error("", ex);
            }
        }

        public void setBroadcastProductionOfSyntax(BroadcastProductionOfSyntax broadcastProductionOfSyntax) {
            this.broadcastProductionOfSyntax = broadcastProductionOfSyntax;
        }

        public void setSmsList(List<String> smsList) {
            this.smsList = smsList;
        }

        public void setDayStr(String dayStr) {
            this.dayStr = dayStr;
        }

        public void setMdnSuffixStartRange(Integer mdnSuffixStartRange) {
            this.mdnSuffixStartRange = mdnSuffixStartRange;
        }

        public void setMdnSuffixEndRange(Integer mdnSuffixEndRange) {
            this.mdnSuffixEndRange = mdnSuffixEndRange;
        }
    }

    public String getMonth() {
        return month;
    }

    public void setMonth(String month) {
        this.month = month;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public List<BroadcastProductionOfUser> getList() {
        return list;
    }

    public String getReportType() {
        return reportType;
    }

    public void setReportType(String reportType) {
        this.reportType = reportType;
    }

    public String getBY_DAY() {
        return BY_DAY;
    }

    public String getBY_MONTH() {
        return BY_MONTH;
    }

    public Date getDay() {
        return day;
    }

    public void setDay(Date day) {
        this.day = day;
    }

    public List<String> getSelectedProviders() {
        return selectedProviders;
    }

    public void setSelectedProviders(List<String> selectedProviders) {
        this.selectedProviders = selectedProviders;
    }

    public Boolean getIsProcessing() {
        return isProcessing;
    }

    public void setIsProcessing(Boolean isProcessing) {
        this.isProcessing = isProcessing;
    }

    public String getUnmatchSms() {
        StringBuilder sb = new StringBuilder();
        for (String string : unmatchSmses) {
            sb.append(string);
        }
        unmatchSms = sb.toString();
        return unmatchSms;
    }

    public List<String> getUnmatchSmses() {
        return unmatchSmses;
    }
    
}
